'''
Created on 05/06/2012

@author: Administradorax
'''
from main.dataStructures import ProcessesQueue
from main.errors import OutOfBoundsError
from main.process import IOInstruction
#-----------------------------------------------------------------------#
#------------------CLASS Physical Memory--------------------------------#
#-----------------------------------------------------------------------#
class PhysicalMemory():
    def __init__(self,aSize, anAddingPolicy):
        self.addingPolicy = anAddingPolicy
        self.processesTable = ProcessesQueue()
        self.size = aSize
        self.mmu = MMU_ContinuousAssignment()
        self.cells = {} # {dir: instruccion}
        i = 0; 
        while (i< aSize):
            self.cells[i]= None
            i+= 1
            
            
    def put (self, anInstruction, anAddress):
        self.cells[anAddress]= anInstruction
        
        
    def get (self, anAddress):
        return self.cells[anAddress]
    
    #Pregunta si la memoria tiene espacio para un proceso de un tamanho
    def hasSpace(self,aSize):
        cont = 0
        conEspacio = 0
        while(cont < len(self.cells)):         #Mientras no vi por todas las celdas
            if(self.cells[cont] == None):#Si la celda en la que estoy posicionado es None
                conEspacio = conEspacio + 1   #Hay una celda mas con espacio
                cont = cont + 1               #Me posicionno en la siguiente celda    
                if(cont < len(self.cells)):
                    if((not conEspacio == aSize) & (not self.cells[cont] == None)):#Sila siguiente celda no es None y el espacio sumado hasta ahora no me alcanza
                        conEspacio = 0
            else:
                cont = cont + 1               #Me posicionno en la siguiente celda           
        return conEspacio >= aSize
    
    
    #Devuelve una lista de pares con el indice donde empieza el espacio vacio y el valor la cant de espacios
    def indexWithSpaces(self):  
        cont =0
        spaces = 0
        base = 0
        noModificarBase = True
        myMap = []
        while(cont < self.size):
            if(self.cells[cont] == None):
                spaces = spaces + 1
                if(noModificarBase):
                    base = cont
                    noModificarBase = False
                if(cont+1 < self.size):
                    if(not (self.cells[cont + 1] == None)):
                        tupla = (base, spaces)
                        myMap.append(tupla)
                        spaces = 0
                        noModificarBase = True
                elif(self.cells[cont] == None):
                        tupla = (base, spaces)
                        myMap.append(tupla)
                        spaces = 0
                        noModificarBase = True
            cont = cont + 1
        return myMap
    
    def fullyIndex(self):  
        cont =0
        filledSpaces = 0
        base = 0
        noModificarBase = True
        myMap = []
        while(cont < self.size):
            if(not self.cells[cont] == None):
                filledSpaces = filledSpaces + 1
                if(noModificarBase):
                    base = cont
                    noModificarBase = False
                if(cont+1 < self.size):
                    if((self.cells[cont + 1] == None)):
                        tupla = (base, filledSpaces)
                        myMap.append(tupla)
                        filledSpaces = 0
                        noModificarBase = True
                elif(not (self.cells[cont] == None)):
                        tupla = (base, filledSpaces)
                        myMap.append(tupla)
                        filledSpaces = 0
                        noModificarBase = True
            cont = cont + 1
        return myMap
    
    
    #Tiene ese proceso en memoria
    def hasThisProcess(self,aProcess):
        yes = False
        if(aProcess in self.processesTable.processes):
            yes = True
        return yes
    
    
    #Si la memoria no tiene espacio para un proceso debe manejarse por otros lados(Compact() Swap())
    def anOtherwise(self,aProcess, aDisk, aCpu):
        if(aProcess.sizeOfAProcess() <= self.allSpace()):              #Si le sirve el espacio compactado, que compacte
                self.compact()
                self.addingPolicy.saveProcessInMemory(aProcess,self)           #Lo agrega en memoria
                self.processesTable.append(aProcess)                           #Lo agrega a la tabla de procesos en memoria
                self.readyProcessesQueue.append(aProcess)                      #Lo agrega a la cola de listos
                self.kernel.interrupt_processArrivedToRPQ()
        elif(self.canSwap(aProcess.sizeOfAProcess())):                  #Si puede hacer swapping, que lo haga
                self.swap(aProcess, aDisk, aCpu)
    
    #Devuelve si puede hacer swapping
    def canSwap(self,aProcessSize,aCPU):
        puede = False
        for p in self.processesTable.processes:
            if(p.sizeOfAProcess() >= aProcessSize) &  (not p==aCPU.currentProcess) &(not self.isUsingAdevice(p,aCPU)):
                puede = True
        return puede
    
    
    #----------------------Metodo de Swapping------------------------------------# 
    def swap(self,aProcess, aDisk, aCPU):
        candidate = self.getSwapVictim(aProcess.sizeOfAProcess(), aCPU)
        aProcess.pcb.baseRegister = candidate.pcb.baseRegister
        self.quitProcessOfMemory(candidate)                 #Lo saca de memoria
        aCPU.readyProcessesQueue.append(aProcess)
        self.processesTable.append(aProcess)                #Lo pone en la tabla de procesos en memoria
        self.addingPolicy.saveProcessInMemory(aProcess,self)#Mete al nuevo proceso en memoria
        return candidate

    def quitProcessOfMemory(self,aProcess):
        cont = aProcess.sizeOfAProcess()                    #Cuantas celdas hay x borrar
        index =  aProcess.pcb.baseRegister                 #La celda desde donde se empezara a eliminar es la base
        while(cont != 0 ):
            self.cells[index] = None
            index = index + 1
            cont = cont - 1
        aProcess.pcb.baseRegister = None                    #Base registro es ahora none
        self.processesTable.cut(aProcess)                   #Lo saca de memoria /no de la cola de listos
        
    def getSwapVictim(self,aProcessSize,aCPU):
        for p in self.processesTable.processes:
            if(p.sizeOfAProcess() >= aProcessSize) &  (not p==aCPU.currentProcess) &(not self.isUsingAdevice(p,aCPU)):
                return p

        
    
    def isUsingAdevice(self,aProcess,aCpu):
        return aProcess in aCpu.io.processesUsingADevice.processes

    #Devuelve todo el espacio sumado
    def allSpace(self):
        cont = 0
        for tupla in self.indexWithSpaces():
            cont = cont + tupla[1]
        return cont
 

    #---------------------------Metodo de compactacion----------------------------------#
    def compact(self):
        cont = 0
        while(not self.isCompacted(self.indexWithSpaces())):
                if(self.cells[cont] == None):
                    self.moveBlock(cont,self.indexWithSpaces())
                cont = cont + 1

          
    def moveBlock(self,index,myMap):
        myList = []
        cont = 0
        cont2 = 0
        fullys = self.fullyIndex()#Busca la proxima celda con una instruccion
        for tupla in myMap:
            if(tupla[0] == index):
                toWalk = index + tupla[1]
                break
        for tupla2 in fullys:#Llega a esa celda y gracias a otra tupla sabe cuantas celdas hay ocupadas de instrucciones contiguas
            if(tupla2[0]==toWalk):
                cantInstructions = tupla2[1]
                break
        while(cont < cantInstructions):#Por cada instruccion, las ira guardando en una lista
            ins = self.cells[toWalk]
            myList.append(ins)
            self.cells[toWalk] = None
            toWalk = toWalk + 1
            cont = cont + 1
        inst = myList[0]# Se fija el propietario de esas instrucciones para actualizar su base
        propietary = self.searchThePropietaryOfInstruction(inst) 
        propietary.pcb.baseRegister = index
        while(cont2 < len(myList)):# Coloca las instrucciones en orden desde la posicion index donde habia un espacio
            self.cells[index] = myList[cont2]
            index = index + 1
            cont2 = cont2 + 1
            
        
            
    def searchThePropietaryOfInstruction(self,anInstruction):
        encontre = False
        candidate = None
        cont = 0
        while( (cont < self.processesTable.length())   & (not encontre)):
            if( anInstruction in (self.processesTable.get(cont)).instructions ):
                encontre = True
                candidate = self.processesTable.get(cont)
            cont = cont + 1
        return candidate
        
                        
    def isCompacted(self,myMap):
        if(len(myMap) == 0):
            return True
        elif(len(myMap) == 1  &  (not self.isInMiddle(myMap[0])) ):
            return True
        else:
            return False
        
    def isInMiddle(self,tupla):
        if(tupla[0] == 0): # Si el espacio esta en el principio
            return True
        else:
            rta = False
            noSeSiEsCompactada = True
            cont = 0
            size = len(self.cells)
            while((cont < len(self.cells)) &  noSeSiEsCompactada):
                if(cont == tupla[0]):
                    if(not tupla[1] == size):
                        rta = True  #Si el espacio es menos al resto del tamanho de memoria que me queda, no esta compactada
                        noSeSiEsCompactada = False
                cont = cont + 1
                size = size - 1
            return rta
        
    def processInstructionsInMemory(self,aProcess):
        instructionsToMemory = 0
        
        for instruction in aProcess.instructions:
            if not isinstance(instruction, IOInstruction):
                instructionsToMemory += 1
        
        return instructionsToMemory

    def neededMemoryCellsOf(self,aProcess):
        return self.processInstructionsInMemory(aProcess)        
                       
              
#-----------------------------------------------------------------------#
#------------------CLASS Physical Memory con paginacion-----------------#
#-----------------------------------------------------------------------#             
class Pagination_PhysicalMemory(PhysicalMemory):
# Como funciona el tema de los frames: si el Dict de memoria tiene el multiplo correspondiente de aFrameSize, quiere decir que esta
# ocupado. Caso contrario, no esta ocupado, y se puede usar.
    def __init__(self,aMemorySize, anAddingPolicy,aFrameSize): 
        PhysicalMemory.__init__(self, aMemorySize, anAddingPolicy)
        self.mmu = MMU_Pagination(aFrameSize) 
        self.frameSize = aFrameSize
        self.cells = {}
        i = 0
        while(i < self.size):
            self.cells[i] = None
            i = i + self.frameSize
 
#---------------------------------------------------------------       
        
    def addInstructionAtFrame(self,frameIndex,instructionPosition,anInstruction):
        self.cells[frameIndex].instructions[instructionPosition]=anInstruction    
        
    def keys(self): #Indices libres
        myList= []
        cont = 0
        while(cont < self.size):
            if((self.cells[cont]) == None): #Si no tiene un frame, no tiene espacio
                myList.append(cont)
            cont = cont + self.frameSize
        return myList
    
    def hasSpace(self,aSize):
        keys = self.keys()
        return not (len(keys) == 0)
    
    def indexWithSpaces(self):
        keys = self.keys()
        if(len(keys) > 0):
            return keys.pop(0)
        else: return None
        
    def hasThisProcess(self,aProcess):
        yes = False
        if(aProcess.actualPage(self.frameSize) in self.mmu.pagesAndFrames.keys()):
            yes = True
        return yes
        
    #----------------------------------------No te enojes ;p

#    def hasSpace(self,aSize):
#        keys = self.cells.keys()
#        current = 0
#        found = False
#        
#        while((not found) & (current < self.size)):  
#            if not keys.__contains__(current):
#                found = True
#            current =+ self.frameSize
#        return found
#    
#    def indexWithSpaces(self):
#    #AAAAAAH MIS OJOS AAAAAH
#    #Pero bueh, es eficiente e.e
#        keys = self.cells.keys()
#        current = 0
#        found = False
#            
#        while not found:
#            if not keys.__contains__(current):
#                found = True
#            current =+ self.frameSize
#        
#        if(found):
#            return current - self.frameSize
#        else:
#            return None
        
    #---------------------------------------------
    
    def anOtherwise(self,aProcess, aDisk, aCpu):#Aca ahora puede hacer swap :D solo asignacion contigua puede hacer compact!
        if(self.canSwap(aProcess,aCpu)):
            self.swap(aProcess,aDisk,aCpu)
            
    def canSwap(self,aProcess, aCpu):
        return (self.processesTable.length() > 1) #Podra hacer swap cuando haya mas de 1 proceso en memoria (por que ese uno soy yo, el currentProcess)
    
    def swap(self,aProcess, aDisk, aCPU):
        self.quitProcessOfMemory(self.getSwapVictim(aProcess,aCPU))
        self.addingPolicy.saveProcessInMemory(aProcess,self)
            
    def getSwapVictim(self,aProcessSize,aCPU):
        for p in self.processesTable.processes:
            if((not p==aCPU.currentProcess) & (not self.isUsingAdevice(p,aCPU))):
                return p
            
            
    def quitProcessOfMemory(self,aProcess):
        for page in aProcess.pcb.processFrames:
            if self.isAFrameInPagesAndFramesTable(page):
                index = self.mmu.pagesAndFrames[page] 
                self.cells[index] = None
                del self.mmu.pagesAndFrames[page] 
        self.processesTable.cut(aProcess)        
                                      
    def isAFrameInPagesAndFramesTable(self,aPage):
        if aPage in self.mmu.pagesAndFrames.keys():
            return True
        else: return False
         
    def neededMemoryCellsOf(self,aProcess):
        neededCells = self.processInstructionsInMemory(aProcess)
        finalNeededCells = 0
        
        while neededCells >= self.frameSize:
            finalNeededCells += self.frameSize
            neededCells -= self.frameSize
        
        if neededCells> 0:
            finalNeededCells += self.frameSize
            
        return finalNeededCells
    
class Frame():
    def __init__(self,aFrameSize):
        self.frameSize = aFrameSize
        self.instructions = {}
        i = 0; 
        while (i < self.frameSize):
            self.instructions[i] = None
            i = i + 1
        
    def hasSpace(self):
        i = 0; 
        has = False
        while (i < self.frameSize):
            if(self.instructions[i]== None):
                has = True
            i = i + 1
        return has
        
    def addInstruction(self,aT,anInstruction):
        if not self.hasSpace():
            raise OutOfBoundsError('El marco esta lleno, no se pueden agregar mas instrucciones en el')
        else:
            self.instructions[aT] = anInstruction
            
     
    
#-----------------------------------------------------------------------#
#------------------CLASS AddingPolicy-----------------------------------#
#-----------------------------------------------------------------------#   
class AddingPolicity():
    
    def saveProcessInMemory(self,aProcess,aPhysicalMemory):
        pass
    
#Paginacion
class Pagination(AddingPolicity):
    
    def saveProcessInMemory(self,aProcess,aPhysicalMemory):
        index = aPhysicalMemory.indexWithSpaces()
        page = aProcess.actualPage(aPhysicalMemory.frameSize)
        self.storeFirstProcessPage(index,aPhysicalMemory,page)
        
    def storeFirstProcessPage(self,anIndex, aMemory, aPage):
        aMemory.mmu.pagesAndFrames[aPage] = anIndex #Pagina y donde empieza en memoria
        aMemory.cells[anIndex] = aPage
    
#Asignacion Continua 
class ContinuousAssignment(AddingPolicity):
        
    #Carga un proceso en memoria y actualiza registros base y limite
    def saveProcessInMemory(self,aProcess,aPhysicalMemory):
        index = self.getTheBaseRegister(aPhysicalMemory,aProcess.sizeOfAProcess())
        aProcess.pcb.baseRegister = index
        for i in aProcess.instructions:
            aPhysicalMemory.put(i,index)
            index = index + 1
        aProcess.pcb.limitRegister = index
        
    
    def getTheBaseRegister(self,aPhysicalMemory,aSize):
        pass
    
class FirstAdjustment(ContinuousAssignment):
      
    def getTheBaseRegister(self,aPhysicalMemory,aSize):
        myMap = aPhysicalMemory.indexWithSpaces()
        myMin = myMap.pop(0)
        if(len(myMap) == 0):              #Si solo tenia un espacio
            return  myMin[0]              #Significa que ese era el unico disponible
        else:
            cont = 0
            while(cont < len(myMap)):     #Si hay mas de un espacio, busco el primero en que pueda hubicar la instruccion
                if(myMin[1] >= aSize):#Si el primer espacio es mayor o = al que necesito
                    return myMin[0]           #Lo tomo
                else:
                    myMin = myMap[cont]
                    cont = cont + 1         #Sino veo el siguiente
            
class BestAdjustment(ContinuousAssignment):   
    
    
    def getTheBaseRegister(self,aPhysicalMemory,aSize):
        myMap = aPhysicalMemory.indexWithSpaces()
        if(len(myMap)== 1):
            return (myMap[0])[0] #Devuelve el indice del primero
        else:
            thisMin = myMap.pop(0)
            cont = 0
            while(cont < len(myMap)): #Si hay mas de un espacio
                if( ((myMap[cont])[1] < thisMin[1]) & ((myMap[cont])[1] >= aSize)):#Si el otro espacio es menor que el actual y es >= a mi tamanho
                    thisMin = myMap[cont]
                    cont = cont + 1
                else:
                    cont = cont + 1
            return thisMin[0]
                                    
            
class WorstAdjustment(ContinuousAssignment):
    
    def getTheBaseRegister(self,aPhysicalMemory,aSize):
        myMap = aPhysicalMemory.indexWithSpaces()
        if(len(myMap)== 1):
            return (myMap[0])[0] #Devuelve el indice del primero
        else:
            thisMax = myMap.pop(0)
            cont = 0
            while(cont < len(myMap)):  #Si hay mas de un espacio
                if( ((myMap[cont])[1] > thisMax[1]) & ((myMap[cont])[1] >= aSize)):#Si el otro espacio es mayor que el actual y es >= a mi tamanho
                    thisMax = myMap[cont]
                    cont = cont + 1
                else:
                    cont = cont + 1
            return thisMax[0]
            
#-----------------------------------------------------------------------#
#------------------CLASS Disk-------------------------------------------#
#-----------------------------------------------------------------------#
  
class Disk():
    def __init__(self):
        self.processInDisk = []
    
    def addProcessToDisk(self,aProcess):
        self.processInDisk.append(aProcess)
        
    def downloadFromDisk(self,aProcess):
        for p in self.processInDisk:
            if(p == aProcess):
                return p
        
    
    
#-----------------------------------------------------------------------#
#------------------CLASS MMU--------------------------------------------#
#-----------------------------------------------------------------------#
class MMU():
    def searchProgram(self):
        pass


class MMU_Pagination(MMU):
    def __init__(self,aFrameSize):
        self.pagesAndFrames = {} # KEY: frame / VALUE: memory position 
        self.frameSize = aFrameSize
        
    def searchProgram(self,aProcess,aMemory):
        currentInstruction = aProcess.instructions.index((aProcess.pcb.currentInstruction)) #Tengo la currentInstruction
        numPag = currentInstruction//self.frameSize                                         #Numero de pagina actual
        desplazamiento = currentInstruction%self.frameSize                                  #desplazamiento
        index = aProcess.pcb.processFrames[numPag]                                          #Indice para buscar en la tabla 
        memoryIndex = self.pagesAndFrames[index]                                            #Indice para buscar la instruccion de la memoria
        instruction = (aMemory.cells[memoryIndex]).instructions[desplazamiento]                              #devuelve la instruccion
        return instruction
        
           

class MMU_ContinuousAssignment(MMU):#Memory Managment Unit
  
    #Retorna al programa de memoria
    def searchProgram(self,aProcess,aMemory):
        logicDir = aProcess.instructions.index((aProcess.pcb.currentInstruction)) #Obtengo la direccion Logica
        if(logicDir > self.limitRegister(aProcess)):
            raise OutOfBoundsError("Error, la direccion logica no puede ser mayor que el registro limite")
        phyticDir = logicDir + aProcess.pcb.baseRegister
        return aMemory.cells[phyticDir]
    
    #Devuelve el registro limite
    def limitRegister(self,aProcess):
        return len(aProcess.instructions)-1
    
